#ifndef _MEMALLOC_TEMPL
#define _MEMALLOC_TEMPL

#include <string>
#include <unordered_map>
#include <unordered_set>
#include "my_assert.h"
#include "memalloc_impl.h"
#include "memalloc.hpp"
#include "../logger/logger.h"

template <class T>
SmartPtr<T> MemAlloc::Alloc(uint64_t elem_count) {
	LOG_FUNCTION("");	
	my_assert(pimpl_);
	return SmartPtr<T>(pimpl_->Alloc(sizeof(T) * elem_count));
}

template <class T>
void MemAlloc::Free(SmartPtr<T>& ptr) {
	LOG_FUNCTION("");	
	my_assert(ptr.pimpl_);
	pimpl_->Free(ptr.pimpl_);
	ptr.pimpl_ = nullptr;
}

template <class T>
PtrAssistDeref<T>::PtrAssistDeref(SmartPtr<T> *ptr, uint64_t idx):
	smart_ptr_(ptr),
	ptr_(nullptr),
	i_(idx) {
	LOG_FUNCTION("");	
	my_assert(smart_ptr_);
	my_assert((idx + 1) * sizeof(T) <= ptr->pimpl->size_);
	ptr_ = smart_ptr_->Get() + idx;
}

template <class T>
PtrAssistDeref<T>::~PtrAssistDeref() {
	LOG_FUNCTION("");	
	my_assert(smart_ptr_);
	smart_ptr_->Release();
}

template <class T>
PtrAssistDeref<T>::operator T() {
	LOG_FUNCTION("");	
	my_assert(ptr_);
	return *ptr_;
}

template <class T>
PtrAssistDeref<T>& PtrAssistDeref<T>::operator= (const T& val) {
	LOG_FUNCTION("");	
	my_assert(ptr_);
	*ptr_ = val;
	return this;
}

template <class T>
ConstPtrAssistDeref<T>::ConstPtrAssistDeref(const SmartPtr<T> *ptr, uint64_t idx): 
	smart_ptr_(ptr),
	ptr_(nullptr),
	i_(idx) {
	LOG_FUNCTION("");	
	my_assert(smart_ptr_);
	my_assert((idx + 1) * sizeof(T) <= ptr->pimpl->size_);
	ptr_ = smart_ptr_->Get() + idx;
}

template <class T>
ConstPtrAssistDeref<T>::~ConstPtrAssistDeref() {
	LOG_FUNCTION("");	
	my_assert(smart_ptr_);
	smart_ptr_->Release();
}

template <class T>
ConstPtrAssistDeref<T>::operator T() const {
	LOG_FUNCTION("");	
	my_assert(ptr_);
	return *ptr_;
}

template <class T>
PtrAssistArrow<T>::PtrAssistArrow(SmartPtr<T> *ptr):
	smart_ptr_(ptr) {
	LOG_FUNCTION("");	
	my_assert(smart_ptr_);
	ptr_ = smart_ptr_->Get();
}

template <class T>
PtrAssistArrow<T>::~PtrAssistArrow() {
	LOG_FUNCTION("");	
	my_assert(smart_ptr_);
	smart_ptr_->Release();
}

template <class T>
T* PtrAssistArrow<T>::operator ->() {
	LOG_FUNCTION("");	
	my_assert(ptr_);
	return ptr_;
}

template <class T>
ConstPtrAssistArrow<T>::ConstPtrAssistArrow(const SmartPtr<T> *ptr):
	smart_ptr_(ptr) {
	LOG_FUNCTION("");	
	my_assert(smart_ptr_);
	ptr_ = smart_ptr_->Get();
}

template <class T>
ConstPtrAssistArrow<T>::~ConstPtrAssistArrow() {
	LOG_FUNCTION("");	
	my_assert(smart_ptr_);
	smart_ptr_->Release();
}

template <class T>
const T* ConstPtrAssistArrow<T>::operator ->() {
	LOG_FUNCTION("");	
	my_assert(ptr_);
	return ptr_;
}

template <class T>
SmartPtr<T>::SmartPtr(const SmartPtr<T>& ptr) {
	LOG_FUNCTION("");	
	this->operator=(ptr);
}

template <class T>
SmartPtr<T>::SmartPtr(SmartPtrImpl *pimpl):
	pimpl_(pimpl)//,
	/*ptr_(nullptr)*/ {
	LOG_FUNCTION("");	
	my_assert(pimpl_);
	
	MemAlloc &mem_alloc = MemAlloc::Instance();
	my_assert(mem_alloc.pimpl_);
	mem_alloc.pimpl_->AddRef(pimpl_);
}


template <class T>
SmartPtr<T>::~SmartPtr() {
	LOG_FUNCTION("");	
	if (!pimpl_)
		return;
	MemAlloc &mem_alloc = MemAlloc::Instance();
	my_assert(mem_alloc.pimpl_);
	mem_alloc.pimpl_->DelRef(pimpl_);
}

template <class T>
SmartPtr<T>& SmartPtr<T>::operator=(const SmartPtr<T>& ptr) {
	LOG_FUNCTION("");	
	pimpl_ = ptr.pimpl_;
//	ptr_ = ptr.ptr_;
	my_assert(pimpl_);

	MemAlloc &mem_alloc = MemAlloc::Instance();
	my_assert(mem_alloc.pimpl_);
	mem_alloc.pimpl_->AddRef(pimpl_);
}

template <class T>
PtrAssistDeref<T> SmartPtr<T>::operator*() {
	LOG_FUNCTION("");	
	return PtrAssistDeref<T>(this);
}

template <class T>
ConstPtrAssistDeref<T> SmartPtr<T>::operator*() const {
	LOG_FUNCTION("");	
	return ConstPtrAssistDeref<T>(this);
}

template <class T>
PtrAssistArrow<T> SmartPtr<T>::operator->() {
	LOG_FUNCTION("");	
	return PtrAssistArrow<T>(this);
}

template <class T>
ConstPtrAssistArrow<T> SmartPtr<T>::operator->() const {
	LOG_FUNCTION("");	
	return ConstPtrAssistArrow<T>(this);
}

template <class T>
PtrAssistDeref<T> SmartPtr<T>::operator[](uint64_t idx) {
	LOG_FUNCTION("");	
	return PtrAssistDeref<T>(this, idx);
}

template <class T>
ConstPtrAssistDeref<T> SmartPtr<T>::operator[](uint64_t idx) const {
	LOG_FUNCTION("");	
	return ConstPtrAssistDeref<T>(this, idx);
}

template <class T>
T* SmartPtr<T>::Get() {
	LOG_FUNCTION("");	
	my_assert(pimpl_);
	MemAlloc &m_alloc = MemAlloc::Instance();
	my_assert(m_alloc.pimpl_);
	return (T*) m_alloc.pimpl_->Lock(pimpl_); 
	//LESHCH мне кажется, в случае если указатель знает, что он залочен, логичнее было бы сразу отдавать указатель, без обращения к аллокатору
}

template <class T>
void SmartPtr<T>::Release() {
	LOG_FUNCTION("");	
	MemAlloc &m_alloc = MemAlloc::Instance();
	my_assert(m_alloc.pimpl_);
	m_alloc.pimpl_->Release(pimpl_);
}

#endif // _MEMALLOC_TEMPL
